package br.com.fiap.servicos;

import br.com.fiap.arquivo.Arquivo;
import br.com.fiap.arquivo.Diretorio;
import br.com.fiap.arquivo.MyNode;
import edu.uci.ics.jung.algorithms.flows.EdmondsKarpMaxFlow;
import edu.uci.ics.jung.algorithms.layout.CircleLayout;
import edu.uci.ics.jung.algorithms.layout.Layout;
import edu.uci.ics.jung.algorithms.shortestpath.DijkstraShortestPath;
import edu.uci.ics.jung.graph.DirectedGraph;
import edu.uci.ics.jung.graph.DirectedSparseMultigraph;
import edu.uci.ics.jung.visualization.RenderContext;
import edu.uci.ics.jung.visualization.VisualizationViewer;
import edu.uci.ics.jung.visualization.control.DefaultModalGraphMouse;
import edu.uci.ics.jung.visualization.control.ModalGraphMouse;
import edu.uci.ics.jung.visualization.decorators.ToStringLabeller;
import edu.uci.ics.jung.visualization.renderers.Renderer;
import edu.uci.ics.jung.visualization.transform.shape.GraphicsDecorator;
import org.apache.commons.collections15.Factory;
import org.apache.commons.collections15.Transformer;

import javax.swing.*;
import java.awt.*;
import java.awt.geom.Ellipse2D;
import java.awt.geom.Point2D;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;


public class BasicDirectedGraph {

    static int edgeCount = 0;
    private static DirectedGraph<MyNode, MyLink> g;
    private static MyNode n1;
    private static MyNode n2;
    private boolean debug = false;
    private List<String> listMessage = new ArrayList<java.lang.String>();



    public JPanel constructGraph() {

        List<Arquivo> lista = new ArrayList<Arquivo>();
        Diretorio dir = new Diretorio();

        lista = dir.getListaAquivo();

        setG(new DirectedSparseMultigraph<MyNode, MyLink>());

        for (Arquivo rm : lista) {

            MyNode n1 = new MyNode();
            n1.setId(rm.getRM());

            for (int i = 1; i < rm.getListaRm().size(); i++) {

                MyNode n2 = new MyNode();
                n2.setId(rm.getListaRm(i));

                getG().addEdge(new MyLink(1.0, 1.0), n1, n2);

            }
        }

        // Layout correspondente ao tipo do grafo
        Layout<MyNode, MyLink> layout = new CircleLayout(getG());
        layout.setSize(new Dimension(780, 480));

        // Visualização
        VisualizationViewer<MyNode, MyLink> component = new VisualizationViewer<MyNode, MyLink>(layout);
        component.setSize(780, 480);

        // Customizar Vertices
        Transformer<MyNode,Paint> vertexPaint = new Transformer<MyNode, Paint>() {
            public Paint transform(MyNode i) {
                return Color.cyan;
            }
        };

        // Customizar Edges
        float dash[] = {10.0f};
        final Stroke edgeStroke = new BasicStroke(1.0f, BasicStroke.CAP_BUTT, BasicStroke.JOIN_MITER, 10.0f, dash, 0.0f);

        Transformer<MyLink, Stroke> edgeStrokeTransformer = new Transformer<MyLink, Stroke>() {
            public Stroke transform(MyLink s) {
                return edgeStroke;
            }
        };

        //component.getRenderContext().setVertexFillPaintTransformer(vertexPaint);
        //component.getRenderContext().setEdgeStrokeTransformer(edgeStrokeTransformer);
        //component.getRenderer().setVertexRenderer(new MyRenderer());
        component.getRenderContext().setVertexLabelTransformer(new ToStringLabeller());
        component.getRenderer().getVertexLabelRenderer().setPosition(Renderer.VertexLabel.Position.CNTR);
        component.getRenderContext().setEdgeLabelTransformer(new ToStringLabeller());


        // PICKING - DragDrop
        final DefaultModalGraphMouse<String, Number> graphMouse = new DefaultModalGraphMouse<String, Number>();
        graphMouse.setMode(ModalGraphMouse.Mode.PICKING);
        component.setGraphMouse(graphMouse);

        return component;
    }



    public void calcUnweightedShortestPath(MyNode nRaiz, MyNode nDestino) {

        DijkstraShortestPath<MyNode, MyLink> alg = new DijkstraShortestPath(getG());
        List<MyLink> listCaminhoCurto = alg.getPath(nRaiz, nDestino);
        Number comprimento = alg.getDistance(nRaiz, nDestino);


        if(isDebug()){
            System.out.println("O caminho mais curto de " + nRaiz + " para " + nDestino + " é: " + listCaminhoCurto);
            System.out.println("O comprimento do caminho é: " + comprimento);
        }
        listMessage.add("O caminho mais curto de " + nRaiz + " para " + nDestino + " é: " + listCaminhoCurto);
        listMessage.add("O comprimento do caminho é: " + comprimento);
    }



    public void calcAlunoIncomunicaveis(MyNode nRaiz){

        DijkstraShortestPath<MyNode, MyLink> alg = new DijkstraShortestPath(getG());
        Map<MyNode, MyLink> mapConnections = alg.getIncomingEdgeMap(nRaiz);
        List<String> list = compLista(mapConnections);

        listMessage.add("O vertice raiz não consegue se conectar com: " + list.toString());
    }

    public  List<String> compLista(Map<MyNode, MyLink> map){

        Diretorio dir = new Diretorio();
        List<Arquivo> lista = dir.getListaAquivo();
        List<String> result = new ArrayList<String>();

        for (Arquivo rm : lista){

            if(!map.containsKey(rm)){
               result.add(rm.getRM());
            }
        }
        return result;
    }



    public void calcDistancia(MyNode node, int distancia){

        DijkstraShortestPath<MyNode, MyLink> alg = new DijkstraShortestPath(getG());
        Map<MyNode, Number> mapDistancia = alg.getDistanceMap(node);
        List<String> list = new ArrayList<String>();

        String str = mapDistancia.toString();
        formatString(str, list);

        String dist = String.valueOf(distancia);
        String build = "";
        Integer aux;

        for(String s: list){

            aux = Integer.parseInt(s.substring(8,9));

            if(aux <= distancia){
                build += s + " ";
            }
        }
        listMessage.add("Fluxo máximo por distância: " + build);
    }


    public void formatString(String str, List<String> list){
        String[] strAp;
        boolean ini = true;

        while(ini){

            strAp = str.split(",");

            for(String s : strAp){
                list.add(s.trim());
            }
            ini=false;
            list.remove(0);
        }
    }


    public void calcMaxFlow(MyNode nRaiz, MyNode nDestino) {

        Transformer<MyLink, Double> capTransformer = new Transformer<MyLink, Double>() {
            public Double transform(MyLink link) {
                return link.capacity;
            }
        };

        Map<MyLink, Double> edgeFlowMap = new HashMap<MyLink, Double>();

        Factory<MyLink> edgeFactory = new Factory<MyLink>() {
            public MyLink create() {
                return new MyLink(1.0, 1.0);
            }
        };

        EdmondsKarpMaxFlow<MyNode, MyLink> alg = new EdmondsKarpMaxFlow(getG(), nRaiz, nDestino, capTransformer, edgeFlowMap, edgeFactory);
        alg.evaluate();

        if(isDebug()){
            System.out.println("O fluxo máximo de alunos intermediarios é: " + alg.getMaxFlow());
            System.out.println("O conjunto de arestas é: " + alg.getMinCutEdges().toString());
        }

        listMessage.add("O fluxo máximo de alunos intermediarios é: " + alg.getMaxFlow());
        listMessage.add("O conjunto de arestas é: " + alg.getMinCutEdges().toString());
    }


    static class MyRenderer implements Renderer.Vertex<MyNode, MyLink> {

        @Override
        public void paintVertex(RenderContext<MyNode, MyLink> rc, Layout<MyNode, MyLink> layout, MyNode vertex) {

            GraphicsDecorator graphicsContext = rc.getGraphicsContext();
            Point2D center = layout.transform(vertex);
            Shape shape = null;
            Color color = null;


            if(vertex.getId().equals("RM62470")) {
                shape = new Ellipse2D.Double(center.getX()-10, center.getY()-10, 10, 20);
                color = new Color(19, 32, 127);
            } else if(vertex.getId().equals("RM62150")) {
                shape = new Ellipse2D.Double(center.getX()-10, center.getY()-10, 30, 20);
                color = new Color(21, 127, 23);
            }

            graphicsContext.setPaint(color);
            graphicsContext.fill(shape);

        }
    }

    class MyLink {

        double capacity;
        double weight;
        int id;

        public MyLink(double weight, double capacity) {
            this.id = edgeCount++;
            this.weight = weight;
            this.capacity = capacity;
        }

        public String toString() {
            return "E" + id;
        }

        @Override
        public boolean equals(Object o) {
            if (this == o) return true;
            if (!(o instanceof MyLink)) return false;

            MyLink myLink = (MyLink) o;

            if (id != myLink.id) return false;

            return true;
        }

        @Override
        public int hashCode() {
            return id;
        }
    }


    public static MyNode getN2() {
        return n2;
    }

    public static void setN2(MyNode n2) {
        BasicDirectedGraph.n2 = n2;
    }

    public static MyNode getN1() {
        return n1;
    }

    public static void setN1(MyNode n1) {
        BasicDirectedGraph.n1 = n1;
    }

    public static DirectedGraph<MyNode, MyLink> getG() {
        return g;
    }

    public static void setG(DirectedGraph<MyNode, MyLink> g) {
        BasicDirectedGraph.g = g;
    }

    public List<String> getListMessage() {
        return listMessage;
    }

    public void setListMessage(List<String> listMessage) {
        this.listMessage = listMessage;
    }

    public boolean isDebug() {
        return debug;
    }

    public void setDebug(boolean debug) {
        this.debug = debug;
    }

}